package no.agens.depth.lib;

import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import com.mdroid.repo.depth_lib_android_.R;

/**
 * A rounded rectangle drawable which also includes a shadow around.
 */
public class CustomShadow extends Drawable {
  final static double COS_45 = Math.cos(Math.toRadians(45));

  final static float SHADOW_MULTIPLIER = 1.5f;
  /*
  * This helper is set by CardView implementations.
  * <p>
  * Prior to API 17, canvas.drawRoundRect is expensive; which is why we need this interface
  * to draw efficient rounded rectangles before 17.
  * */
  static RoundRectHelper sRoundRectHelper;
  final int mInsetShadow; // extra shadow to avoid gaps between card and shadow
  final RectF mCardBounds;
  private final int mShadowStartColor;
  private final int mShadowEndColor;
  Paint mPaint;
  Paint mCornerShadowPaint;
  Paint mEdgeShadowPaint;
  float mCornerRadius;
  Path mCornerShadowPath;
  // updated value with inset
  float mMaxShadowSize;
  // actual value set by developer
  float mRawMaxShadowSize;
  // multiplied value to account for shadow offset
  float mShadowSize;
  // actual value set by developer
  float mRawShadowSize;
  private boolean mDirty = true;
  private boolean mAddPaddingForCorners = true;

  /**
   * If shadow size is set to a value above max shadow, we print a warning
   */
  private boolean mPrintedShadowClipWarning = false;

  public CustomShadow(Resources resources, int backgroundColor, float radius, float shadowSize,
      float maxShadowSize) {
    mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
    mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
    mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
    mPaint.setColor(backgroundColor);
    mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
    mCornerShadowPaint.setStyle(Paint.Style.FILL);
    mCornerRadius = (int) (radius + .5f);
    mCardBounds = new RectF();
    mEdgeShadowPaint = new Paint(mCornerShadowPaint);
    mEdgeShadowPaint.setAntiAlias(false);
    setShadowSize(shadowSize, maxShadowSize);

    CustomShadow.sRoundRectHelper = new RoundRectHelper() {
      @Override
      public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint) {
        canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
      }
    };
  }

  static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
      boolean addPaddingForCorners) {
    if (addPaddingForCorners) {
      return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
    } else {
      return maxShadowSize * SHADOW_MULTIPLIER;
    }
  }

  static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
      boolean addPaddingForCorners) {
    if (addPaddingForCorners) {
      return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
    } else {
      return maxShadowSize;
    }
  }

  /**
   * Casts the value to an even integer.
   */
  private int toEven(float value) {
    int i = (int) (value + .5f);
    if (i % 2 == 1) {
      return i - 1;
    }
    return i;
  }

  public void setAddPaddingForCorners(boolean addPaddingForCorners) {
    mAddPaddingForCorners = addPaddingForCorners;
    invalidateSelf();
  }

  @Override public void setAlpha(int alpha) {
    mPaint.setAlpha(alpha);
    mCornerShadowPaint.setAlpha(alpha);
    mEdgeShadowPaint.setAlpha(alpha);
  }

  @Override protected void onBoundsChange(Rect bounds) {
    super.onBoundsChange(bounds);
    mDirty = true;
  }

  void setShadowSize(float shadowSize, float maxShadowSize) {
    if (shadowSize < 0 || maxShadowSize < 0) {
      throw new IllegalArgumentException("invalid shadow size");
    }
    shadowSize = toEven(shadowSize);
    maxShadowSize = toEven(maxShadowSize);
    if (shadowSize > maxShadowSize) {
      shadowSize = maxShadowSize;
      if (!mPrintedShadowClipWarning) {
        mPrintedShadowClipWarning = true;
      }
    }
    if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
      return;
    }
    mRawShadowSize = shadowSize;
    mRawMaxShadowSize = maxShadowSize;
    mShadowSize = (int) (shadowSize * SHADOW_MULTIPLIER + mInsetShadow + .5f);
    mMaxShadowSize = maxShadowSize + mInsetShadow;
    mDirty = true;
    invalidateSelf();
  }

  @Override public boolean getPadding(Rect padding) {
    int vOffset = (int) Math.ceil(
        calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius, mAddPaddingForCorners));
    //        int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
    //                mAddPaddingForCorners));
    //        padding.set(hOffset, vOffset, hOffset, vOffset);
    padding.set(0, vOffset, 0, 0);
    return true;
  }

  @Override public void setColorFilter(ColorFilter cf) {
    mPaint.setColorFilter(cf);
    mCornerShadowPaint.setColorFilter(cf);
    mEdgeShadowPaint.setColorFilter(cf);
  }

  @Override public int getOpacity() {
    return PixelFormat.TRANSLUCENT;
  }

  @Override public void draw(Canvas canvas) {
    if (mDirty) {
      buildComponents(getBounds());
      mDirty = false;
    }
    canvas.translate(0, -mRawShadowSize / 2);
    drawShadow(canvas);
    canvas.translate(0, +mRawShadowSize / 2);
    sRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint);
  }

  private void drawShadow(Canvas canvas) {
    final float edgeShadowTop = -mCornerRadius - mShadowSize;
    final float insetVertical = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
    final float insetHorizontal = -mInsetShadow;
    // LT top
    int saved = canvas.save();
    canvas.translate(mCardBounds.left + insetHorizontal, mCardBounds.top + insetVertical);
    canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
    canvas.drawRect(0, edgeShadowTop, mCardBounds.width() - 2 * insetHorizontal,
        -mCornerRadius + mShadowSize, mEdgeShadowPaint);
    canvas.restoreToCount(saved);

    // RT right
    saved = canvas.save();
    canvas.translate(mCardBounds.right - insetHorizontal, mCardBounds.top + insetVertical);
    canvas.rotate(90f);
    canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
    canvas.restoreToCount(saved);
  }

  private void buildShadowCorners() {
    RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
    RectF outerBounds = new RectF(innerBounds);
    outerBounds.inset(-mShadowSize, -mShadowSize);

    if (mCornerShadowPath == null) {
      mCornerShadowPath = new Path();
    } else {
      mCornerShadowPath.reset();
    }
    mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
    mCornerShadowPath.moveTo(-mCornerRadius, 0);
    mCornerShadowPath.rLineTo(-mShadowSize, 0);
    // outer arc
    mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
    // inner arc
    mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
    mCornerShadowPath.close();
    float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
    mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
        new int[] { mShadowStartColor, mShadowStartColor, mShadowEndColor },
        new float[] { 0f, startRatio, 1f }, Shader.TileMode.CLAMP));

    // we offset the content shadowSize/2 pixels up to make it more realistic.
    // this is why edge shadow shader has some extra space
    // When drawing bottom edge shadow, we use that extra space.
    mEdgeShadowPaint.setShader(
        new LinearGradient(0, -mCornerRadius + mShadowSize, 0, -mCornerRadius - mShadowSize,
            new int[] { mShadowStartColor, mShadowStartColor, mShadowEndColor },
            new float[] { 0f, .5f, 1f }, Shader.TileMode.CLAMP));
    mEdgeShadowPaint.setAntiAlias(false);
  }

  private void buildComponents(Rect bounds) {
    // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
    // We could have different top-bottom offsets to avoid extra gap above but in that case
    // center aligning Views inside the CardView would be problematic.
    final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER;
    mCardBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset,
        bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);
    buildShadowCorners();
  }

  float getCornerRadius() {
    return mCornerRadius;
  }

  void setCornerRadius(float radius) {
    radius = (int) (radius + .5f);
    if (mCornerRadius == radius) {
      return;
    }
    mCornerRadius = radius;
    mDirty = true;
    invalidateSelf();
  }

  void getMaxShadowAndCornerPadding(Rect into) {
    getPadding(into);
  }

  float getShadowSize() {
    return mRawShadowSize;
  }

  void setShadowSize(float size) {
    setShadowSize(size, mRawMaxShadowSize);
  }

  float getMaxShadowSize() {
    return mRawMaxShadowSize;
  }

  void setMaxShadowSize(float size) {
    setShadowSize(mRawShadowSize, size);
  }

  float getMinWidth() {
    final float content =
        2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
    return content + (mRawMaxShadowSize + mInsetShadow) * 2;
  }

  float getMinHeight() {
    final float content = 2 * Math.max(mRawMaxShadowSize,
        mCornerRadius + mInsetShadow + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
    return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
  }

  public void setColor(int color) {
    mPaint.setColor(color);
    invalidateSelf();
  }

  static interface RoundRectHelper {
    void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint);
  }
}